package com.gframework.mybatis.dao;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.lang.Nullable;

import com.gframework.mybatis.dao.mybatis.provider.ProviderUtils;

/**
 * 这是一个拦截所有Mybatis的DAO方法“实现了{@link IMybatisDAO}接口的类”的抽象拦截器.
 * <p>
 * <strong>使用此类的前提是DAO互不调用原则，即进入dao方法后，不能够再继续调用其他的dao方法。</strong>
 * <p>
 * 本类本身无特殊业务逻辑，但是任何DAO方法内都可以知道当前所处的dao位置，执行的类方法等信息，并可以保存一些数据，直到dao方法结束后被销毁等。
 * <p>
 * 此AOP时dao最外层的aop，所以他的order为{@link Integer#MIN_VALUE}。
 * <p>
 * 本类主要包含以下几个主要功能：
 * <ol>
 * <li>可以取得当前执行的dao的类和方法信息。</li>
 * <li>保存一些本地线程变量，直到dao方法结束后被销毁，你无需再关心销毁问题。</li>
 * <li>可以判断是否已经进入了dao方法。</li>
 * </ol>
 * 
 * @since 1.0.0
 * @author Ghwolf
 * 
 * @see ProviderUtils ProviderUtils 可以通过这个工具类来获取Mybatis Dao相关信息
 */
@Order(Integer.MIN_VALUE)
@Aspect
public class MybatisDaoAop {

	/**
	 * 当前dao信息.
	 * 
	 * @author Ghwolf
	 */
	static class LocalDaoInfo {

		/** 存储线程本地变量的集合 */
		@Nullable
		private Map<String, Object> map;
		/** 当前执行的方法 */
		private final Method method;
		/** mybatis dao接口类型 */
		private final Class<?> interfaceClass;

		public LocalDaoInfo(Method method, Class<?> interfaceClass) {
			this.method = method;
			this.interfaceClass = interfaceClass;
		}

		/**
		 * 设置一个变量
		 */
		public void setParam(String key, Object value) {
			if (key == null) {
				return;
			}
			if (this.map == null) {
				this.map = new HashMap<>();
			}
			this.map.put(key, value);
		}

		/**
		 * 获取一个变量
		 */
		public Object getParam(String key) {
			return map == null || key == null ? null : map.get(key);
		}

		public Method getMethod() {
			return this.method;
		}

		public Class<?> getInterfaceClass() {
			return this.interfaceClass;
		}

	}

	/**
	 * 本地延迟初始化的变量缓存
	 */
	private static final ThreadLocal<Object> LAZY_LOCAL = new ThreadLocal<>();

	/**
	 * 获取本地缓存的LocalDaoInfo类对象，这是一个延迟初始化的过程，如果一直没有掉用过本类对象，那么是不会对LocalDaoInfo类进行创建并初始化的。
	 */
	private static LocalDaoInfo getLocalDaoInfo(){
		Object obj = LAZY_LOCAL.get();
		if (obj == null) return null ;
		if (obj instanceof ProceedingJoinPoint) {
			ProceedingJoinPoint point = (ProceedingJoinPoint) obj;
			MethodSignature ms = (MethodSignature) point.getSignature();
			// mybatis 是接口编程模型，每个自动生成的代理类只有一个唯一的接口
			LocalDaoInfo info = new LocalDaoInfo(ms.getMethod(), point.getTarget().getClass().getInterfaces()[0]);
			LAZY_LOCAL.set(info);
			return info ;
		} else {
			return (LocalDaoInfo) obj ;
		}
	}
	
	/**
	 * 获取一个本地变量.
	 * <p>
	 * 如果当前不处于dao方法内，执行此方法是无效的。
	 * 
	 * @param key 变量key值
	 * @return 如果存在则返回，否则返回null
	 */
	@Nullable
	public static Object getLocalParam(@Nullable String key) {
		LocalDaoInfo info = getLocalDaoInfo();
		if (info == null) return null;
		return info.getParam(key);
	}

	/**
	 * 设置一个本地变量，这个变量会在dao方法结束后被销毁.
	 * <p>
	 * 如果当前不处于dao方法内，执行此方法是无效的。
	 * 
	 * @param key 变量key值，如果为null，则不保存
	 * @param value 要保存的变量
	 */
	public static void setLocalParam(@Nullable String key, @Nullable Object value) {
		LocalDaoInfo info = getLocalDaoInfo();
		if (info == null) return;
		info.setParam(key, value);
	}

	/**
	 * 判断当前是否处于dao方法内
	 * 
	 * @return 如果处于返回true，否则返回false
	 */
	public static boolean isInDao() {
		return LAZY_LOCAL.get() != null;
	}

	/**
	 * 取得当前dao方法的Method对象.
	 * <p>
	 * 如果当前不处于dao方法内，执行此方法是无效的。
	 * 
	 * @return 返回当前dao方法的method对象，没有返回null
	 */
	@Nullable
	public static Method getDaoMethod() {
		LocalDaoInfo info = getLocalDaoInfo();
		if (info == null) return null;
		return info.getMethod();
	}

	/**
	 * 取得当前dao方法的Class对象.
	 * <p>
	 * 如果当前不处于dao方法内，执行此方法是无效的。
	 * 
	 * @return 返回当前dao方法的class对象，没有返回null
	 */
	@Nullable
	public static Class<?> getDaoClass() {
		LocalDaoInfo info = getLocalDaoInfo();
		if (info == null) return null;
		return info.getInterfaceClass();
	}
	
	/**
	 * 拦截操作
	 */
	@Around("execution(* com.gframework.mybatis.dao.IMybatisDAO+.*(..))")
	public Object around(ProceedingJoinPoint point) throws Throwable {
		try {
			LAZY_LOCAL.set(point);
			return point.proceed();
		} finally {
			LAZY_LOCAL.remove();
		}
	}

}